package in.co.codedoc.struts;

import in.co.codedoc.json.JSONArrayValue;
import in.co.codedoc.json.JSONObjectValue;
import in.co.codedoc.json.JSONValue;

import java.io.Writer;
import java.util.Collection;

import org.apache.struts2.StrutsException;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import com.opensymphony.xwork2.util.ValueStack;

@StrutsTag(name="json-ref", tldTagClass="in.co.codedoc.struts.JSONRefTag", description="JSONRef tag.")
public class JSONRef
	extends JSONStrutsComponentBase
{

	public JSONRef(ValueStack stack)
	{
		super(stack);
	}

	@Override
	public boolean start(Writer writer)
	{
		Object rootValue = stack.findValue(getRoot());
		
		if((rootValue != null) && (rootValue instanceof JSONValue))
		{
			JSONValue value = FindJSONValue((JSONValue)rootValue,getPath());
			SetValue(getName(),value);
			
			//If an object set its properties as values too.
			if(value instanceof JSONObjectValue)
			{
				SetPropertyValues(getName(), (JSONObjectValue)value);
			}
		}
		
		return true;
	}
	
	private JSONValue FindJSONValue(JSONValue root, String path)
	{
		if(root == null)
		{
			return root;
		}
		
		if(path == null || path == "")
		{
			return root;
		}
		
		if(path.charAt(0) == '.')
		{
			path = path.substring(1);
		}
		
		String pathComponent;
		String remainderPath;
		if(path.charAt(0) == '[')
		{
			int nextRSquareIndex = path.indexOf(']');
			
			if(nextRSquareIndex < 0)
			{
				throw new StrutsException
				(
					"Incorrect syntax when dereferencing " + getPath() + " with respect to " + 
					getRoot() + "(to be named " + getName() + ")"
				);
			}
			
			pathComponent = path.substring(1, nextRSquareIndex);
			remainderPath = nextRSquareIndex < path.length() -1 ? path.substring(nextRSquareIndex+1) : "";
		}
		else
		{
			int nextDotIndex = path.indexOf('.');
			int nextLSquareIndex = path.indexOf('[');
			
			int nextStopperIndex = -1;
			
			if(nextDotIndex > 0 && nextLSquareIndex > 0)
			{
				nextStopperIndex = Math.min(nextDotIndex, nextLSquareIndex);
			}
			else if(nextDotIndex > 0)
			{
				nextStopperIndex = nextDotIndex;
			}
			else if(nextLSquareIndex > 0)
			{
				nextStopperIndex = nextLSquareIndex;
			}
			else
			{
				
			}
			
			pathComponent = nextStopperIndex > 0 ? path.substring(0,nextStopperIndex) : path;
			remainderPath = (nextStopperIndex > 0 && nextStopperIndex < (path.length() -1)) ? path.substring(nextStopperIndex+1) : "";
		}
		
		if(root instanceof JSONObjectValue)
		{
			return FindJSONValue(((JSONObjectValue)root).GetProperty(pathComponent),remainderPath);
		}
		else if(root instanceof JSONArrayValue)
		{
			return FindJSONValue(((JSONArrayValue)root).GetEntry(Integer.parseInt(pathComponent)),remainderPath);
		}
		else
		{
			return null;
		}
	}
	
	@Override
	public boolean end(Writer writer, String body)
	{
		UnsetValues();
		return super.end(writer, body);
	}

	public String getRoot() 
	{
		return root;
	}

	@StrutsTagAttribute(description="The root object with reference to which the path expression defines the reference", type="String", required=true)
	public void setRoot(String root)
	{
		this.root = root;
	}

	private String root;
	
	public String getPath()
	{
		return path;
	}

	@StrutsTagAttribute(description="The path relative to the root", type="String", required=false)
	public void setPath(String path)
	{
		this.path = path;
	}
	
	private String path;

	public String getName()
	{
		return name;
	}

	@StrutsTagAttribute(description="The Name of the reference", type="String", required=true)
	public void setName(String name)
	{
		this.name = name;
	}
	
	private String name;
	private Collection<String> propertyNames;
}
